---
slug: 函数式组件 #路径，不能重复
title: 函数式组件 # 标题
authors: [] # 作者，在authors.yml配置
tags: [facebook, hello, docusaurus] # 标签，在tags.yml配置
toc_min_heading_level: 2 # 最小展示标题
toc_max_heading_level: 5 # 最大展示标题
---

## Hooks组件/函数式组件

> 函数式组件没有this

#### 函数式组件绑定方法

> 永远都需要记住**调用方法必须存在于闭包环境下**


##### 对于没有参数的

> vote函数是闭包调用，不会在渲染阶段直接执行

```jsx

const vote = ()=>{
	console.log('vote');
}

<div>
<Button onClick={vote}}>up</Button>
</div>
```

##### 对于有参数的

> 对于有参数的而言，直接`vote('up')`会在渲染时候直接执行

```jsx
const vote = type=>{
	console.log(type);
}

<div>
<Button onClick={()=>vote('up')}}>up</Button>
</div>
```


> 必须闭包调用父组件传过来的方法

```jsx
const Father = ()=>{
	const onData = type=>{
		console.log(type);
	}
	return (
		<Children onData={onData}/>
	)
}

/**
*
*必须闭包调用
*/
const Children = ({onData})=>{
	const dataHandler = type=>onData(type);
	return (
		<>
			<Button onClick={dataHandler('up')}></Button>
			{/**或者**/}
			<Button onClick={()=>onData('up')}></Button>
		</>
	)
}


```

### useState

> React Hook函数之一，目的是在函数组件中使用状态，并且后期基于状态进行修改，可以让组件进行更新

```jsx
import {useState} from 'react';
let [value,setValue] = useState(0);
```

- 初始化值，返回数组
- 返回的数组第一项为状态值，第二项目为修改状态的函数
- 执行setValue函数`setValue(value)`:
**修改value的值后通知视图更新**

#### useState不支持差异修改

```jsx
let [vote,setVote] = useState({
    up:0,
    down:0
});
//想改就必须展开原有的vote
const up = ()=>{
    setVote({
        ...vote,
        up:vote.up+1,
    })
}
```

#### 为何++不可

> up++ 会执行两步操作
>
> **1. 返回up当前的值**
> **2.然后将up自增1**

因此实际上setUp收到的是未自增前的旧值

```jsx
const [up,setUp] = useState(0);

//这样不行
setUp(up++);
```


#### 官方对于useState对于对象的set建议

> 官方推荐的写法是分开

```jsx
let [up,setUp] = useState(0);
let [down,setDown] = useState(0);
const up = ()=>{
    setUp(up+1);
}

const down = ()=>{
	setDown(down+1);
}

```


#### useState会因为闭包问题导致值拿不到最新

> 因为setState函数触发的时机形成了闭包条件，导致闭包内部的上下文环境是初始化的上下文环境

以下`console.log()`输出的会是10而不是11，因为up拿到的函数拿到的up指向的是初始化的up

```jsx
export function Test(){
	let [up,setUp] = useState(10);
	const voteUp = ()=>{
	    setUp(up+1);
	    console.log(up);
	}
}
```

同样地

```jsx
const loop = ()=>{
    for (let i = 0; i < 10; i++) {
        setLoopValue(1+loopValue);//只更新一次,结果11
        //why?
        //1.更新一次在于react把操作放入了队列中，只执行一次以优化性能
        //2. 结果11为每一次的更新由于闭包原因只取到了初始化的loopValue即10
    }
}
```


#### react对于useState内部的性能优化

> useState 每次更新前会使用Object.is来与上一次状态值做比较，如果一致则不会修改状态也不会让视图更新


### useEffect

**必须是响应式值**

**不能放在任何的条件判断里**

> 生命周期hooks

#### 语法 useEffect(callBack)

> useEffect(callBack) 第一次渲染和任何状态值的变更都会触发callBack


```jsx

const [name,setName] = useState('inital')
const [x,setX] = useState(0)
useEffect(()=>{
	console.log(name) //可以拿到最新的name
})

const handler = ()=>{
	setName(name+='dd');//每次调用handler都会触发上面的useEffect的hooks回调
}

const handler2 = ()=>{
	setX(x+1);//每次调用handler2都会触发上面的useEffect的hooks回调
}
```

#### 语法 useEffect(callBack,[])

> 只有在第一次渲染才会触发callBack

```jsx
const [name,setName] = useState('inital')

useEffect(()=>{
	console.log(name) //只有第一次渲染才会触发
},[])

const handler = ()=>{
	setName(name+='dd');//每次调用handler都不会触发上面的useEffect的hooks
}
```

#### 语法 useEffect(callBack, \[...args\])

> args变化的时候再触发useEffect


##### 监听useState的值

```jsx
const [name,setName] = useState('inital')
const [x,setX] = useState(0)
useEffect(()=>{
	console.log(name) //可以拿到最新的name,且只监听name的变动
},[name])

const handler = ()=>{
	setName(name+='hello ');//每次调用handler都会触发上面的useEffect的hooks回调
}

const handler2 = ()=>{
	setName(x+1);//每次调用handler2不会触发上面的useEffect的hooks回调
}
```


##### 监听props的值

```jsx
import React,{useEffect} from 'react';

//结构外部传入的data(名字根据外部写的名来算，也可以{name}/{url}...)
export const Comp = function({data}){
useEffect(()=>{
console.log(data)
},[data])
return (
	<>

	</>
)
}

```

##### 返回小函数以捕捉组件销毁时的生命周期


```jsx
useEffect(()=>{
	return ()=>{
		//组件被销毁时执行
	}
})
```

##### useEffect不能放在任何的条件判断或循环里，必须要能触发到useEffect

```jsx

    const [num,setNum] = useState(0);

    if(num > 5){
        useEffect(()=>{
            console.log(num)
        })  //不行，会报错，必须放在最外层且必须能触发到的
    }
	for (let i = 0; i < num; i++) {
	    useEffect(()=>{
	        console.log('call back return func');  //也不行，必须在最外层
	    })
	}
```

###### 但是确实是想在一些判断后调用useEffect执行一些方法怎么办呢

```jsx
const [num,setNum] = useState(0);
	useEffect(()=>{
		if(num > 5){
			console.log(num)
		}
	},[num])//形成对num值的监听
```

##### useEffect内部如果返回值的话必须返回一个函数供组件销毁时调用

###### 比如说我想在组件初始化时候调用后台接口获取数据

```jsx
import React,{useEffect,useState} from 'react';
import {Button} from "antd";


const  queryData = ()=>{
    return new Promise((resolve,reject)=>{
        resolve('hello world');
    });
}

export const UseLayOutEffectHooks = function () {

    const [num,setNum] = useState(0);

    useEffect(async () => {
        const data = await queryData();
        console.log(data);
    }, []);  //这样是不行的，因为async修饰的函数会返回Promise，不是函数

	//==================> 解决方案
	//Promise调用链方式
	useEffect(() => {
	    queryData().then(res=>{
	        console.log(res);
	    })
	}, []);
	//就想用async/await
	useEffect(() => {
	    const query = async ()=>{
	        const data =  await queryData();
	        console.log(data);
	    }
	    query();
	}, []);

    return (
        <> </>
    )

};
```



### useLayoutEffect

> useLayoutEffect会阻止浏览器渲染真实DOM，去执行callBack
> **真实DOM已经创建了，只是浏览器是否渲染**

##### useLayoutEffect与useEffect区别

> 组件渲染完成后->打印日志->浏览器绘制

```jsx
useLayoutEffect(()=>{
console.log('hello world');
},[])
```

> 组件渲染完成后->浏览器绘制->打印日志

```jsx
useEffect(()=>{
console.log('hello world');
},[])
```

### 获取ref的几种方式

#### 通过函数赋值的方式获取ref

```jsx
const GetRefByFunction = ()=>{
    //通过函数获取ref
    let divRef = null;
    useEffect(() => {
        console.log(divRef,'通过函数的方式获取ref')
    }, []);
    return (
        <>
            <div id={'d1'} ref={e=> {divRef = e}}></div>
        </>
    )
}
```


#### 通过React.createRef方式获取ref

```jsx
const GetRefByReactCreateRef = ()=>{

    let divRef = React.createRef();

    useEffect(() => {
        console.log(divRef.current,'通过React.createRef获取ref')
    }, []);

    return(
        <>
            <div ref={divRef}>div</div>
        </>
    )
}
```

#### 通过useRef的hooks获取ref

```jsx
const GetRefByUseRef = ()=>{
    const divRef = useRef(null);

    useEffect(() => {
        console.log(divRef.current,'通过useRef的hooks获取ref')
    }, []);

    return(
        <>
            <div ref={divRef}>div</div>
        </>
    )
}
```


### 基于useMemo计算缓存

> 一个简单地算式会让函数式组件重新执行，消耗太大，使用useMemo可以在依赖项变化时候执行

```jsx
const UseMemoStudy = ()=>{
    const [up,setUp] = useState(0);

    const [down,setDown] = useState(0);

    //计算支持人占总人数的比例

    //useMemo计算缓存，免除重复执行函数
    let rate = useMemo(()=>{
        const total = up+down;
        let rate = 0;
        if (total > 0){
            rate  = ((up/total)*100).toFixed(2);
        }
        return rate;
    },[up,down]);




    return (
        <>
            <h2>支持人数占总人数的比例:{rate+"%"}</h2>
            <div style={containerStyle}>
                <div>
                    支持人数:{up}
                </div>
                <div>
                    反对人数:{down}
                </div>
                <Divider/>
                <Button onClick={()=>setUp(up+1)} style={{marginRight:'20px'}}>支持</Button>
                <Button onClick={()=>setDown(down+1)} type={'primary'}>
                反对
                </Button>
            </div>
        </>    )
}
```

### useCallBack保存函数引用

> useCallBack可以保留函数引用，可以节省性能

```jsx
useCallback(()=>{
    change('up');
},[])
```

#### 对于参数而言

> 仅当回调需要依赖外部变量时候才需要声明依赖项

##### 如果完全不依赖外部变量则不需要依赖项

```jsx
useCallback((type)=>{
console.log('type');
},[])
```

##### 如果依赖外部变量则需要依赖项

```jsx
useCallback((type)=>{
console.log(vote.up,vote.down);
},[vote])
```


### 自定义hooks(其实就是封装个方法,内部是对hooks的二次改造)

> 使用自定义hooks可以将某些组件逻辑提取到可重用的函数中（实现某些逻辑的重用）

> 自定义hooks可以像原生hooks那样记住某些状态

#### 自定义hooks最好是use开头

原本的useState不支持差异修改，也就是如果`useState({up:0,down:0})`这样的则`setState({up:1})`时down会丢失，因此我们可以自己封装一个支持差异修改的hooks

```jsx
/**
 *如果是const [state,setState] = useParticleState({up:0,down:0});
 * setState({up:100})这样调用的话
 *
*/
const useParticleState = (initValue)=>{
    let [particleState, setParticleState] = useState(initValue);

    const setParticle = (updateValue)=>{
        setParticleState ({
            ...particleState,  //展开原本的{up:0,down:0}
            ...updateValue  //将新改的值替换掉原来的值 {up:100,down:0}
        })  //返回一个支持差异修改的setState方法
    }

    return [particleState,setParticle];  //返回
}
```

##### 使用方式跟普通hooks没区别

```jsx
export const HooksByMeComp = function () {

    const [state,setState] = useParticleState({up:0,down:0});
    return (
        <>
            <div>
                支持人数:{state.up}
            </div>

            <div>
                反对人数:{state.down}
            </div>
            <Divider/>
            <Button type={'primary'}
            onClick={()=>setState({up:state.up+1})}>
            +
            </Button>
            <Button type={'primary'}
            onClick={()=>setState({down:state.down+1})}>
            -
            </Button>
        </>
    )

};
```

### 父子组件通信

> 父子组件通信必须是父传子，因为react是单向数据流


#### 祖先通信

> react祖先通信需要使用上下文

#### React.createContext(上下文)

`src/ThemeContext.js`

```js
import React from 'react';
const ThemeContext = React.createContext();
export default ThemeContext
```

##### 祖先组件使用Context

> Context提供了一个Provider标签，想给谁传递就包住谁

```jsx
import React, {useState} from 'react';
import ThemeContext from "../../ThemeContext";
/**
 * 祖先组件
 * @returns {Element}
 * @constructor
 */
export const AncestorsComponents = function () {

    const [up, setUp] = useState(10);
    const [down, setDown] = useState(10);

    const change = type=>{
        switch (type) {
            case 'up':
                setUp(up+1);
                break;
            case 'down':
                setDown(down+1);
                break;
        }
    }

    return (
        <>
			<ThemeContext.Provider value={{up,down,change}}>{}
				<VoteFather/>
			</ThemeContext.Provider>
        </>
    )

};

```

##### 子组件使用context

> 子组件使用useContext接收子组件

```jsx
import ThemeContext from "../../ThemeContext";
const VoteFather = ()=>{
    //这个ThemeContext得跟父组件传递来的ThemeContext一样
    const {up,down} =  useContext(ThemeContext);
    return(
        <>
            <div>
                支持人数:{up}
            </div>
            <div>
                反对人数:{down}
            </div>
            <Divider style={{marginBottom:'20px'}}/>
            <VoteChildren/>
        </>
    )
}
```

##### 孙子组件使用context

> 孙子组件使用context和子组件一样都是useContext

```jsx
const VoteChildren  = ()=>{

    const {change} = useContext(ThemeContext);

    return (
        <>
            <Button type={'primary'}
            style={{marginRight:"20px"}} onClick={()=>change('up')}>
            支持
            </Button>
            <Button type={'primary'} danger onClick={()=>change('down')}>
            反对
            </Button>
        </>
    )
}
```


##### 子组件接收还有别的办法 Context.Consumer

> ThemeContext.Consumer 内部要求一个函数，函数参数context就是上面传来的参数

* 优点在于可以直接渲染复杂逻辑

```jsx
const VoteChildren  = ()=>{
    return (
        <>
            <ThemeContext.Consumer>
                {
                    context=>{
                        console.log(context,'context')
                        return <>
                            <Button type={'primary'}
                            style={{marginRight:"20px"}}
                            onClick={()=>context.change('up')}>
                            支持
                            </Button>
                            <Button type={'primary'} danger
                            onClick={()=>context.change('down')}>
                            反对
                            </Button>
                        </>
                    }

                }
            </ThemeContext.Consumer>
        </>    )
}
```

#### 如果祖先组件有一个Provider而父组件还有一个Provider，听谁的？

**听内层的，内层的provider会覆盖上层的provider**

